<?php
/**
 *
 *
 * $Rev: 8 $
 * $Author: thejahweh $
 * $Date: 2010-09-14 09:40:11 +0200 (Di, 14 Sep 2010) $
 * $Id: pluginLoader.class.php 8 2010-09-14 07:40:11Z thejahweh $
 * $URL: https://allone.googlecode.com/svn/trunk/plugins/core/pluginLoader.class.php $
 * @author: Adrian Liechti <info@jahweh.ch>
 * @copyright: 2010 Adrian Liechti, GPL3 and CC3 BY-SA
 */

class pluginLoader {
	
	public $plugins;
	public $register;
	public $cfg;
	public $dirRoot;
	public $dirPlugins;
	public $dirConfig;
	public $cfgPathPlugins		= 'plugins.xml.php';
	public $cfgPathRegister		= 'register.xml.php';
	public $cfgPathConfig		= 'config.xml.php';
	public $isLoaded			= false;
	
	public function __construct ($dirConfig = '/cfg') {
		$this->dirPlugins	= dirname(dirname(dirname(__FILE__)));
		$this->dirRoot		= dirname($this->dirPlugins);
		if ($dirConfig[0] === '/') {
			$this->dirConfig	= $this->dirRoot.$dirConfig;
		} else {
			$this->dirConfig	= $dirConfig;
		}
	}
	
	public function loadPlugins () {
		$this->plugins	= array();
		
		$pluginsCFG = $this->getPluginsByCFG();
		if ($pluginsCFG === false or count($pluginsCFG->children()) === 0) {
			$this->getPluginsByDir();
		}
		
		return true;
	}
	
	public function loadPluginsFresh () {
		$this->plugins	= array();
		
		$pluginsCFG = $this->getPluginsByCFG(false);
		if ($pluginsCFG === false or count($pluginsCFG->children()) === 0) {
			$this->getPluginsByDir();
		} else {
			foreach ($pluginsCFG->children() as $plugin) {
				$this->loadPlugin((string)$plugin['id']);
			}
		}
		
		$this->setPluginsOrder();
		
		return true;
	}
	
	public function registerInclude () {
		$t = new timeStamp('pluginLoader:loadPlugins', false, false);
		$tInc = new timeStamp('Detailed pluginRegisterInclude', false, false);
		$t->start('pluginRegisterInclude');
		u::o('pluginRegisterInclude', 'pluginLoader:loadPlugins');
		foreach ($this->plugins as $p) {
			u::o('<span class="red">'.$p->id.'</span>', 'pluginLoader:loadPlugins', 1);
			$tInc->start($p->id);
			$p->registerInclude();
			$tInc->end();
		}
		$t->end();
	}
	
	public function getConfigByCFG () {
		$path = $this->dirConfig.'/'.$this->cfgPathConfig;
		$this->cfg = u::loadInvisibleCFG($path, '<cfg />');
	}
	
	public function getPluginsByCFG ($load = true) {
		$path = $this->dirConfig.'/'.$this->cfgPathPlugins;
		if (file_exists($path) 
			and $pluginCFG = u::loadInvisibleSimpleXMLFile($path) 
			and get_class($pluginCFG) === 'SimpleXMLElement')
		{
			u::o('getPluginsByCFG: <span class="path">'.$this->cfgPathPlugins.'</span>', 'pluginLoader:getPluginsByCFG');
			if ($load === true) {
				$this->plugins	= array();
				foreach ($pluginCFG as $plugin) {
					if ($this->loadPlugin((string)$plugin['id'], NULL, $plugin)) {
						u::o($plugin['id'].' (true)', 'pluginLoader:getPluginsByCFG', 1);
					} else {
						u::o($plugin['id'].' (false)', 'pluginLoader:getPluginsByCFG', 1);
					}
				}
				return true;
			} else {
				return $pluginCFG;
			}
		} else {
			return false;
		}
	}
	
	public function getRegisterByCFG () {
		$path = $this->dirConfig.'/'.$this->cfgPathRegister;
		if (file_exists($path) 
			and $this->register = u::loadInvisibleSimpleXMLFile($path) 
			and get_class($this->register) === 'SimpleXMLElement')
		{
			u::o('getRegisterByCFG: <span class="path">'.$this->cfgPathRegister.'</span>', 'pluginLoader:getRegisterByCFG');
			
			return true;
		} else {
			return false;
		}
	}
	
	public function savePluginsToCFG () {
		$dom = new DOMDocument('1.0');
		$dom->formatOutput = true;
		$plugins = $dom->createElement('plugins');
		$plugins->setAttribute('update', time());
		$dom->appendChild($plugins);
		u::o('savePluginsToCFG', 'pluginLoader:savePluginsToCFG');
		foreach ($this->plugins as $plugin) {
			u::o($plugin->id, 'pluginLoader:savePluginsToCFG', 1);
			$dom_sxe = dom_import_simplexml($plugin->xml);
			if (!$dom_sxe) {
				u::o('Error while converting XML', 'pluginLoader:savePluginsToCFG', 2);
				continue;
			}
			$dom_sxe = $dom->importNode($dom_sxe, true);
			$plugins->appendChild($dom_sxe);
		}
		return u::saveInvisibleFile($this->dirConfig.'/'.$this->cfgPathPlugins, $dom->saveXML());
	}
	
	public function saveRegisterToCFG () {
		
		u::callRegisterFunctions('registerSave');
		
		$dom = new DOMDocument('1.0');
		$dom->formatOutput = true;
		
		u::o('savePluginsToCFG', 'pluginLoader:savePluginsToCFG');
		$dom_sxe = dom_import_simplexml($this->register);
		if (!$dom_sxe) {
			u::o('Error while converting XML', 'pluginLoader:savePluginsToCFG', 2);
			return false;
		}
		$dom_sxe = $dom->importNode($dom_sxe, true);
		if ($dom_sxe->hasAttribute('update')) {
			$dom_sxe->removeAttribute('update');
		}
		$dom_sxe->setAttribute('update', time());
		$dom->appendChild($dom_sxe);
		
		return u::saveInvisibleFile($this->dirConfig.'/'.$this->cfgPathRegister, $dom->saveXML());
	}
	
	/**
	 * Ladet ein Plugin
	 * @param string $pluginName
	 * @param string $pluginDir = false
	 * @return bool
	 */
	public function loadPlugin ($pluginName, $pluginDir = NULL, $xml = false) {
		
		if ($pluginDir === NULL) $pluginDir =& $this->dirPlugins;
		
		$plugin = new plugin($pluginName, $pluginDir, $xml);
		if ($plugin->isLoaded) {
			if (isset($this->cfg->$pluginName) === false) {
				$this->cfg->addChild($pluginName);
				$this->cfg->save();
			}
			$plugin->cfg = $this->cfg->$pluginName;
			$this->plugins[$pluginName] =& $plugin;
			$this->$pluginName =& $plugin;
			return true;
		}
		
		return false;
	}
	
	public function setPluginsOrder () {
		$pluginsOrder = $this->getPluginsOrder();
		if ($pluginsOrder !== false) {
			$plugins = array();
			foreach ($pluginsOrder as $m) {
				$plugins[$m] =& $this->plugins[$m];
			}	
			$this->plugins =& $plugins;
		} else {
			return false;
		}
	}
	
	/**
	 * Gibt die Liste aller Plugins die in einem Ordner 
	 * vorhanden sind, geordnet nach den Abhängigkeiten, zurück.
	 * @param string $pluginFolder
	 * @return array('plugin','plugin')
	 * TODO Beschreibung ändern
	 * TODO getPluginsOrder in Config einbeziehen!
	 */
	public function getPluginsOrder () {
		
		if (!is_array($this->plugins)) return false;
		$list		= array();
		$listAll	= array();
		
		uasort($this->plugins, array($this, 'fetchPluginPriority'));
		
		u::o('getPluginList', 'pluginLoader:getPluginsOrder', 1);
		foreach ($this->plugins as $plugin) {
			if (!in_array($plugin->id, $listAll)) {
				u::o($plugin->id, 'pluginLoader:getPluginsOrder', 2);
				$this->fetchPluginOrder($plugin->id, $list, $listAll, 2);
			}
		}
		if (u::o('List:', 'pluginLoader:getPluginsOrder:list', 2)) {
			foreach ($list as $n => $m) {
				u::o($m, 'pluginLoader:getPluginsOrder:list', 3);
			}
		}
		return $list;
	}
	
	private function fetchPluginPriority(&$a, &$b) {
		$c = (int)$a->priority;
		$d = (int)$b->priority;
		if ($c === $d) {
			return 0;
		}
		return ($c < $d) ? -1 : 1;
	}
	
	/**
	 * Prüft ob das Plugin noch weitere Plugins braucht.
	 * - Ja, werden diese Plugins geprüft und anschliessend,
	 *   dieses Plugin in der Liste eingetragen.
	 * - Nein, das Plugin wird gleich in der Liste eingetragen.
	 * @param string $pluginName
	 * @param array &$plugin
	 * @param array &$list
	 * @param array &$listAll
	 * @param int $count
	 * @return bool
	 */
	private function fetchPluginOrder ($pluginName, &$list, &$listAll, $count = 0) {
		if (false === array_key_exists($pluginName, $this->plugins)) return false;
		$plugin =& $this->plugins[$pluginName];
		$listAll[] = $pluginName;
		$count++;
		foreach ($plugin->requires->children() as $requiredName => $required) {
			/**
			 * TODO Unterstützung von "Group" requires.
			 */
			if ($requiredName === 'plugin' and !in_array($required['name'], $listAll)) {
				u::o($required['name'], 'pluginLoader:getPluginsOrder', $count);
				if (false === $this->fetchPluginOrder((string)$required['name'], $list, $listAll, $count)) {
					u::o('"- false', 'pluginLoader:getPluginsOrder', $count);
					/**
					 * TODO fetchPluginOrder, Fehlermeldungsausbau
					 */
					if (!isset($required['optional'])) return false;
				}
			}
		}
		$list[] = $pluginName;
		return true;
	}
	
	/**
	 * Lädt alle Plugins in einem Ordner
	 * @param string $dir
	 * @return bool
	 */
	public function getPluginsByDir ($dir = false) {
		if ($dir === false) $dir = $this->dirPlugins;
		u::o('getPluginsByDir: <span class="path">'.$dir.'</span>', 'pluginLoader:getPluginsByDir');
		if (is_dir($dir)) {
			$handle	= opendir($dir);
			//$t = new timeStamp('getPluginsByDir', false, false);
			while (($fn = readdir($handle)) !== false) {
				if ($fn !== '.' and $fn !== '..') {
					//$t->start($fn);
					if ($this->loadPlugin($fn, $dir)) {
						u::o($fn.' (true)', 'pluginLoader:getPluginsByDir', 1);
					} else {
						u::o($fn.' (false)', 'pluginLoader:getPluginsByDir', 1);
					}
					//$t->end();
				}
			}
			closedir($handle);
			return true;
		}
		return false;
	}
	
	/**
	 * Erstellt das Register, aller Plugins
	 * @return none
	 */
	public function loadRegister () {
		$this->register = new DOMDocument('1.0');
		$this->register->formatOutput = true;
		$registerRoot = $this->register->createElement('register');
		$this->register->appendChild($registerRoot);
		
		u::o('Register Plugin:', 'pluginLoader:loadRegister');
		foreach ($this->plugins as $pluginName => $plugin) {
			u::o($pluginName, 'pluginLoader:loadRegister', 1);
			foreach ($plugin->register->children() as $registerChild) {
				$this->fetchRegisterNode($registerChild, $registerRoot, $pluginName);
			}
		}
		$this->register = simplexml_import_dom($this->register);
		return true;
	}
	
	private function fetchRegisterNode (&$node, &$parent, &$pluginName) {
		$nodeName = $node->getName();
		u::o($nodeName, 'pluginLoader:loadRegister', 2);
		
		if (count($node->attributes())) {
			$createNewNode = true;
		} else {
			if ($nodeList = $parent->getElementsByTagName($nodeName) and $nodeList->length === 1) {
				$newNode = $nodeList->item(0);
				$createNewNode = false;
			} else {
				$createNewNode = true;
			}
		}
		
		if ($createNewNode === true) {
			$newNode = $this->register->createElement($nodeName);
			$parent->appendChild($newNode);
			foreach ($node->attributes() as $name => $value) {
				$newNode->setAttribute($name, $value);
			}
		}
		
		if (count($node->children()) === 0) {
			$newNode->setAttribute('pluginName', $pluginName);
		} else {
			foreach ($node->children() as $child) {
				$this->fetchRegisterNode($child, $newNode, $pluginName);
			}
		}
		return true;
	}
	
	/**
	 * Gibt die angegebene Registergruppe zurück oder den Registereintrag, 
	 * bei angabe vom Registereintrag. Bei einem Fehlerfall gibt es false zurück.
	 * @param string $groupName
	 * @param string $entryName = false
	 * @return SimpleXMLElement(RegisterEntry)|array(RegisterEntry,...)|false
	 */
	public function &getRegister($groupName, $entryName = false) {
		if (array_key_exists($groupName, $this->register)) {
			if ($entryName === false) {
				return $this->register[$groupName];
			} elseif (array_key_exists($this->register[$groupName], $entryName)) {
				return $this->register[$groupName][$entryName];
			}
		}
		return false;
	}
	
	public function hasPlugins () {
		if (!is_array($this->plugins) or ($pluginsCount = count($this->plugins)) === 0) {
			return false;
		} else {
			return $pluginsCount;
		}
	}
	
	public function hasRegister () {
		return (get_class($this->register) === 'SimpleXMLElement');
	}
	
	public function isLoaded ($pluginName) {
		return in_array($pluginName, $this->plugins);
	}
	
	public function &getPlugin ($pluginName) {
		if ($this->isLoaded($pluginName)) {
			return false;
		} else {
			return $this->plugins[$pluginName];
		}
	}
	
	public function destruct () {
		/**
		 * TODO Call Shutdown Functions!
		 */
		$this->cfg->destruct();
	}
	
}
?>